/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

import jmmo.util.MapSet

/**
 * Base common methods for container support traits.
 * These traits contains implementations [[jmmo.engine.property.ObservableContainer]]
 * and [[jmmo.engine.property.ModifiableObservableContainer]] methods.
 * @author Tomas Shestakov
 */
trait ChildrenSupport {

  /**
   * Add specified child observable to the container.
   * Calling this method not change `ObservableContainer.observables` list
   * @param child observable to be added to the container
   */
  protected def addChildObs(child: Observable)

  /**
   * Remove specified child observable from the container
   * Calling this method not change `ObservableContainer.observables` list
   * @param child observable to be removed from the container
   */
  protected def removeChildObs(child: Observable)

  /**
   * Updates all child observables filters and subscriptions.
   * Call this method if `ObservableContainer.observables` list has many changes
   * or first time after `ObservableContainer.observables` list was formed.
   */
  protected def subscribeChildrenObs()
}

/**
 * Implements [[jmmo.engine.property.ChangeListeners]] methods
 * @author Tomas Shestakov
 */
trait ChildrenListenersSupport extends ObservableContainer with ChildrenSupport {

  def addChangeListener(listener: Listener, filter: Filter = Identifiable.passAll) {
    listenerToFilter.get(listener) match {
      //case Some(`filter`) =>  //replace listener even new filter is the same as already exists for specified listener
      case Some(_) =>
        removeChangeListener(listener)
        addChangeListener(listener, filter)
      case None =>
        val childListener = childChangeListener(listener)
        observables.withFilter (filter) foreach { obs =>
          obs.addChangeListener(childListener, filter)
        }
        listenerToFilter += (listener -> filter)
    }
  }

  def removeChangeListener(listener: Listener) {
    listenerToFilter.get(listener) match {
      case Some(filter) =>
        val childListener = childChangeListener(listener)
        observables.withFilter (filter) foreach { obs =>
          obs.removeChangeListener(childListener)
        }
        listenerToFilter -= listener
      case None =>
    }
  }

  def fireChangeEvent(event: ChangeEvent) {
    listenerToFilter.keys foreach (jmmo.util.applyIfDefined(_, event))
  }

  protected def addChildObs(observable: Observable) {
    val addEvent = AddObsEvent(this, observable)
    listenerToFilter withFilter { case (_, filter) => filter(observable) } foreach { case (listener, _) =>
      observable.addChangeListener(childChangeListener(listener))
      //Inform listeners about adding new observable
      jmmo.util.applyIfDefined(listener, addEvent)
    }
  }

  protected def removeChildObs(observable: Observable) {
    val removeEvent = RemoveObsEvent(this, observable)
    listenerToFilter withFilter { case (_, filter) => filter(observable) } foreach { case (listener, _) =>
      observable.removeChangeListener(childChangeListener(listener))
      //Inform listeners about removing observable
      jmmo.util.applyIfDefined(listener, removeEvent)
    }
  }

  protected def subscribeChildrenObs() {
    //subscribe to childChangeListener observables which pass some filter
    listenerToFilter foreach { case (listener, filter) =>
      observables.withFilter (filter) foreach { observable =>
        observable.addChangeListener(childChangeListener(listener))
      }
    }
  }

  /**
   * Create special listener for child observables from original listener
   * @param listener original listener from `addChangeListener` method
   * @return new instance of special listener for child observables
   */
  protected def childChangeListener(listener: ChangeListeners.Listener): ChangeListeners.Listener =
    ChangeListeners.wrap(listener, this)

  protected var listenerToFilter = scala.collection.mutable.Map.empty[ChangeListeners.Listener, Identifiable.Filter]

}

/**
 * Container extends this trait will not be denoted in a `chain` of change events as if it did not exist.
 */
trait TransparentListenersSupport extends ChildrenListenersSupport {

  /**
   * @param listener original listener from `addChangeListener` method
   * @return `listener` parameter without changes
   */
  override protected def childChangeListener(listener: ChangeListeners.Listener) = listener
}

/**
 * Implements `observable` and `observables` methods of [[jmmo.engine.property.ObservableContainer]].
 * @author Tomas Shestakov
 */
trait ObservableChildrenSupport extends ObservableContainer {
  
  def observable(ids: Identifier*) = {
    if (ids.isEmpty) {
      Some(this)
    }
    else {
      obsMap.get(ids.head) match {
        case Some(container: ObservableContainer) => container.observable(ids.tail: _*)
        case Some(observable) if ids.tail.isEmpty => Some(observable)
        case _ => None
      }
    }
  }

  def observables = obsMap.values
  
  protected var obsMap = Map.empty[Identifier, Observable]
}

/**
 * Implements `observable` and `observables` methods of [[jmmo.engine.property.ObservableContainer]]
 * and `alterable` and `alterables` methods of [[jmmo.engine.property.AlterableContainer]].
 * @author Tomas Shestakov
 */
trait AlterableObservableChildrenSupport extends ObservableChildrenSupport with AlterableObservableContainer {

  override def observable(ids: Identifier*): Option[AlterableObservable] = super.observable(ids: _*).asInstanceOf[Option[AlterableObservable]]

  override def observables: Iterable[AlterableObservable] = super.observables.asInstanceOf[Iterable[AlterableObservable]]

  def alterable(ids: Identifier*) = observable(ids: _*)

  def alterables = observables
}

/**
 * Implements `addObservable` and `removeObservable` of [[jmmo.engine.property.ModifiableObservableContainer]].
 * If child observables added without using `addObservable`
 * then `ChildrenListenersSupport.subscribeChildrenObs` must be executed.
 * Method `removeObservable` must be executed after remove any child observable
 * @author Tomas Shestakov
 */
trait ModifiableChildrenSupport extends ModifiableObservableContainer with ObservableChildrenSupport with ChildrenSupport {

  def addObservable(obs: Observable) {
    obsMap += (obs.id -> obs)
    addChildObs(obs)
  }

  def removeObservable(obs: Observable) {
    obsMap -= obs.id
    removeChildObs(obs)
  }

  val alter: PartialFunction[Alteration, Unit] = {
    case AddAlteration(elem: Observable) => addObservable(elem)
    case RemoveAlteration(elem: Observable) => removeObservable(elem)
  }
}

/**
 * Implements `ObservableValue.serializableCopy` method for [[jmmo.engine.property.ObservableContainer]]
 * @author Tomas Shestakov
 */
trait SerializableSilentObservableContainerSupport extends ObservableContainer {

  def serializedCopy(filter: Identifiable.Filter = Identifiable.passAll) = SilentObservableContainer(
    observables withFilter(filter) map (obs => obs.serializedCopy(filter)), id)
}

/**
 * Base trait for creation [[jmmo.engine.property.ObservableContainer]] instances.
 * @author Tomas Shestakov
 */
trait ContainerSupport extends ObservableChildrenSupport with ChildrenListenersSupport with SerializableSilentObservableContainerSupport

/**
 * Base trait for creation [[jmmo.engine.property.ModifiableObservableContainer]] instances.
 * @author Tomas Shestakov
 */
trait ModifiableContainerSupport extends ModifiableChildrenSupport with ChildrenListenersSupport with SerializableSilentObservableContainerSupport

/**
 * Base trait for creation [[jmmo.engine.property.ObservableContainer]] instances where
 * container will not be denoted in a `chain` of change events as if it did not exist.
 * @author Tomas Shestakov
 */
trait TransparentContainerSupport extends ObservableChildrenSupport with TransparentListenersSupport with SerializableSilentObservableContainerSupport

/**
 * Base trait for creation [[jmmo.engine.property.ModifiableObservableContainer]] instances where
 * container will not be denoted in a `chain` of change events as if it did not exist.
 * @author Tomas Shestakov
 */
trait TransparentModifiableContainerSupport extends ModifiableChildrenSupport with TransparentListenersSupport with SerializableSilentObservableContainerSupport

/**
 * Base trait for creation [[jmmo.engine.property.AlterableContainer]] instances.
 * @author Tomas Shestakov
 */
trait AlterableContainerSupport extends AlterableObservableChildrenSupport with ModifiableChildrenSupport with ChildrenListenersSupport with SerializableSilentObservableContainerSupport

/**
 * Base trait for creation [[jmmo.engine.property.AlterableContainer]] instances where
 * container will not be denoted in a `chain` of change events as if it did not exist.
 * @author Tomas Shestakov
 */
trait TransparentAlterableContainerSupport extends AlterableObservableChildrenSupport with ModifiableChildrenSupport with ChildrenListenersSupport with SerializableSilentObservableContainerSupport

